using System;
using System.Collections.Generic;

namespace InfiniTec.Threading
{
	internal class ResourceReservation
	{
		private readonly IResourceConstraint _Constraint;
		private readonly List<ResourceReservationEntry> _RegisteredOperations = new List<ResourceReservationEntry>();

		public ResourceReservation(IResourceConstraint constraint)
		{
			if (constraint == null) throw new ArgumentNullException("constraint");
			_Constraint = constraint;
		}

		public int Count { get { return _RegisteredOperations.Count; } }

		public IResourceConstraint Constraint { get { return _Constraint; } }

		public void RegisterOperation(AsyncOperation operation)
		{

			if (operation == null) throw new ArgumentNullException("operation");

			ResourceReservationEntry entry = GetParentReservation(operation);
			if (entry != null)
			{
				if (entry.ConstraintUsedByChildOperation)
				{
					entry = null;
				}
				else
				{
					entry.ConstraintUsedByChildOperation = true;
				}
			}
			_RegisteredOperations.Add(new ResourceReservationEntry(operation, entry));

			ExecuteAllowedPendingOperations();
		}

		private void ExecuteAllowedPendingOperations() {
			int maxAllowedOperations = _Constraint.AllowedParalellOperations;

			int runningOperations = 0;

			foreach (ResourceReservationEntry entry in _RegisteredOperations)
			{
				if (entry.Operation.OperationStatus != OperationStatus.ExecutionPending)
				{
					runningOperations++;
				}
				else
				{
					if (entry.ParentReservation != null)
					{
						AsyncOperation.StartAsync(entry.Operation);
						runningOperations++;
					}
				}
			}
			
			for (int i = 0; i < _RegisteredOperations.Count; i++)
			{
				ResourceReservationEntry reservation = _RegisteredOperations[i];

				if (reservation.Operation.OperationStatus != OperationStatus.ExecutionPending) continue;

				if (i > 0)
				{
					if (reservation.ConstraintUsedByChildOperation)
					{
						maxAllowedOperations++;
					}
				}
				if (runningOperations < maxAllowedOperations)
				{
					AsyncOperation.StartAsync(reservation.Operation);
					runningOperations++;
				}
			}
		}

		private ResourceReservationEntry GetParentReservation(AsyncOperation operation)
		{
			return GetParentReservation(operation, _RegisteredOperations.Count-1);
		}

		private ResourceReservationEntry GetParentReservation(AsyncOperation operation, int index)
		{
			for (int i = index; i >= 0; i--)
			{
				if (IsParentOperation(_RegisteredOperations[i].Operation, operation)) return _RegisteredOperations[i];
			}
			return null;
		}

		private static bool IsParentOperation(AsyncOperation assumed, AsyncOperation childOperation)
		{
			if (assumed == null) throw new ArgumentNullException("assumed");
			if (childOperation == null) throw new ArgumentNullException("childOperation");

			AsyncOperation parentOperation = childOperation.ParentOperation;
			while (parentOperation != null)
			{
				if (assumed == parentOperation) return true;
				parentOperation = parentOperation.ParentOperation;
			} 

			return false;
		}

		

		public void ReleaseOperation(AsyncOperation operation)
		{
			if (operation == null) throw new ArgumentNullException("operation");

			int index = _RegisteredOperations.FindIndex(reservationEntry => operation == reservationEntry.Operation);
			if (index == -1)
			{
				throw new InvalidOperationException(string.Format("The operation {0} (id {1}) is not registered on this reservation instance.", operation.Name,
				                                                  operation.Id));
			}

			ResourceReservationEntry entry = _RegisteredOperations[index];

			_RegisteredOperations.RemoveAt(index);

			if (entry.ParentReservation != null && index < _RegisteredOperations.Count)
			{
				entry = entry.ParentReservation;

				if (entry != null)
				{
					entry.ConstraintUsedByChildOperation = false;
					for (int i = index; i < _RegisteredOperations.Count; i++)
					{
						if (_RegisteredOperations[i].Operation.OperationStatus != OperationStatus.ExecutionPending) continue;

						ResourceReservationEntry reservation = GetParentReservation(_RegisteredOperations[i].Operation);
						if (reservation != null)
						{
							_RegisteredOperations[i].ParentReservation = entry;
							entry.ConstraintUsedByChildOperation = true;
							break;
						}
					}
				}
			}

			ExecuteAllowedPendingOperations();
		}
	}
}